package cn.yx.common.redis.service;

import cn.yx.common.redis.pojo.RedisCacheObject;
import org.springframework.data.redis.connection.RedisServerCommands;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;

import java.time.Instant;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * <p>Redis服务类</p>
 *
 * @author Wgssmart
 */
@Component
public class RedisService {

    private final RedisTemplate<String, Object> redisTemplate;

    public RedisService(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public static final String KEY_LOCK_PREFIX = "key_lock::";

    /**
     * 保存
     *
     * @param key
     * @param value
     */
    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 保存
     *
     * @param key
     * @param value
     * @param expiration
     * @param timeUnit
     */
    public void set(String key, Object value, long expiration, TimeUnit timeUnit) {
        redisTemplate.opsForValue().set(key, value, expiration, timeUnit);
    }

    /**
     * key不存在则保存
     *
     * @param key
     * @param value
     */
    public Boolean setIfAbsent(String key, Object value) {
        return redisTemplate.opsForValue().setIfAbsent(key, value);
    }

    /**
     * key不存在则保存
     *
     * @param key
     * @param value
     * @param expiration
     * @param timeUnit
     */
    public Boolean setIfAbsent(String key, Object value, long expiration, TimeUnit timeUnit) {
        return redisTemplate.opsForValue().setIfAbsent(key, value, expiration, timeUnit);
    }

    /**
     * 获取缓存数据的字节数
     *
     * @param key
     * @return
     */
    public Long size(String key) {
        return redisTemplate.opsForValue().size(key);
    }

    /**
     * 设置失效时间
     *
     * @param key
     * @param expiration
     * @param timeUnit
     */
    public Boolean expire(String key, long expiration, TimeUnit timeUnit) {
        return redisTemplate.expire(key, expiration, timeUnit);
    }

    /**
     * 设置失效时间点
     *
     * @param key
     * @param instant
     */
    public Boolean expireAt(String key, Instant instant) {
        return redisTemplate.expireAt(key, instant);
    }

    /**
     * 获取多长时间后失效，单位s
     *
     * @param key
     * @return
     */
    public Long getExpire(String key) {
        return redisTemplate.getExpire(key);
    }

    /**
     * 获取多长时间后失效，指定时间单位
     *
     * @param key
     * @return
     */
    public Long getExpire(String key, TimeUnit timeUnit) {
        return redisTemplate.getExpire(key, timeUnit);
    }

    /**
     * 判断key是否存在
     *
     * @param key
     * @return
     */
    public Boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 获取缓存对象
     *
     * @param key
     * @return
     */
    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    /**
     * 删除指定的key
     *
     * @param key
     */
    public Boolean delete(String key) {
        return redisTemplate.delete(key);
    }

    /**
     * 删除指定的keys
     *
     * @param keys
     * @return
     */
    public Long delete(List<String> keys) {
        return redisTemplate.delete(keys);
    }

    /**
     * 删除指定的keys
     *
     * @param keys
     * @return
     */
    public Long delete(Collection<String> keys) {
        return redisTemplate.delete(keys);
    }

    /**
     * 根据pattern获取缓存的Keys
     *
     * @param keyPattern key pattern
     * @param count      指定个数
     * @return
     */
    public Set<String> listKeys(String keyPattern, long count) {
        Cursor<byte[]> cursor = redisTemplate.executeWithStickyConnection(redisConnection
                -> redisConnection.scan(ScanOptions.scanOptions().match(keyPattern).count(count).build()));
        if (Objects.isNull(cursor)) {
            return null;
        }
        Set<String> cacheKeys = new HashSet<>();
        while (cursor.hasNext()) {
            cacheKeys.add(new String(cursor.next()));
        }
        return cacheKeys;
    }

    /**
     * 根据pattern获取缓存的对象
     *
     * @param keyPattern key pattern
     * @param count      指定个数
     * @return
     */
    public Set<RedisCacheObject> listValues(String keyPattern, long count) {
        Cursor<byte[]> cursor = redisTemplate.executeWithStickyConnection(redisConnection
                -> redisConnection.scan(ScanOptions.scanOptions().match(keyPattern).count(count).build()));
        if (Objects.isNull(cursor)) {
            return null;
        }
        Set<RedisCacheObject> cacheValues = new HashSet<>();
        while (cursor.hasNext()) {
            cacheValues.add(getCacheObject(new String(cursor.next())));
        }
        return cacheValues;
    }

    /**
     * 获取缓存对象，封装key，value，size，expiration，TimeUnit
     *
     * @param key
     * @param timeUnit
     * @return
     */
    public RedisCacheObject getCacheObject(String key, TimeUnit timeUnit) {
        if (!hasKey(key)) {
            return null;
        }
        ValueOperations<String, Object> valueOperation = redisTemplate.opsForValue();
        RedisCacheObject redisCacheObject = new RedisCacheObject();
        redisCacheObject.setKey(key);
        redisCacheObject.setValue(valueOperation.get(key));
        redisCacheObject.setSize(valueOperation.size(key));
        redisCacheObject.setExpiration(getExpire(key, timeUnit));
        redisCacheObject.setTimeUnit(timeUnit);
        redisCacheObject.setExpirationAt(Instant.now().plusSeconds(getExpire(key)));
        return redisCacheObject;
    }

    /**
     * 获取缓存对象，封装key，value，size，expiration，TimeUnit
     *
     * @param key
     * @return
     */
    public RedisCacheObject getCacheObject(String key) {
        return getCacheObject(key, TimeUnit.SECONDS);
    }

    /**
     * 获取key总数
     *
     * @return
     */
    public Long dbSize() {
        return redisTemplate.execute(RedisServerCommands::dbSize);
    }

    /**
     * 获取Redis服务器信息
     *
     * @return
     */
    public Properties info() {
        return redisTemplate.execute((RedisCallback<Properties>) RedisServerCommands::info);
    }

    /**
     * 创建分布式锁
     *
     * @param key
     * @param expiration
     * @param timeUnit
     * @return 返回false表示通信失败或已经加锁，返回true表示加锁成功
     */
    public Boolean lock(String key, long expiration, TimeUnit timeUnit) {
        return redisTemplate.opsForValue().setIfAbsent(KEY_LOCK_PREFIX + key, 1, expiration, timeUnit);
    }

    /**
     * 释放分布式锁
     *
     * @param key
     * @return 返回false表示通信失败或解锁失败，返回true表示解锁成功
     */
    public Boolean unlock(String key) {
        return redisTemplate.delete(KEY_LOCK_PREFIX + key);
    }

}
